﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.Text;
using System.Xml.Linq;
using XMLCharSheets;


namespace CombatAutomationTheater
{
    [DataContract(Namespace = "")]
    [KnownType(typeof (PathfinderCharacter_HP))]
    [KnownType(typeof (PathfinderCharacter_WoundsVigor))]
    public abstract class PathfinderCharacter : CharacterSheet
    {
        private string _rollResults = "";
        [DataMember]
        private List<PathfinderLightSource> _mobileLightSources = new List<PathfinderLightSource>();

        private CharacterVisionType _characterVisionType = new CharacterVisionType();
        public PathfinderCharacter(string name, List<Trait> curTraits) :
            base(name, curTraits)
        {
        }

        [DataMember]
        public CharacterVisionType CharacterVisionType
        {
            get { return _characterVisionType; }
            set { _characterVisionType = value; }
        }
        
        [DataMember]
        private PathfinderLightType _inLight = new PathfinderLightType(PathfinderLightType.LightType.Normal);
        public PathfinderLightType InLight
        {
            get { return _inLight; }
            set { _inLight = value; }
        }


        public override String RollResults
        {
            get { return _rollResults; }
            set { _rollResults = value; }
        }

        public override String ChosenAttackValue
        {
            get { return ""; }
        }

        public bool SingleAttackOnly { get; set; }

        public override void RollInitiative()
        {
            _inLight = new PathfinderLightType(PathfinderLightType.LightType.Normal);
            var matchingNumeric =
                Traits.FirstOrDefault(x => (x as PathfinderNumericTrait) != null && x.TraitLabel.Equals("Initiative")) as PathfinderNumericTrait;
            if (matchingNumeric == null)
                CurInitiative = -1;
            else
            {
                var rollIni = new PathfinderDicePool(1, 20, matchingNumeric.TraitValue);
                rollIni.Roll();
                CurInitiative = rollIni.TotalValue;
            }
        }

        internal override void NewRound()
        {
            if (HasTrait("Regeneration"))
            {
                int regenValue = FindNumericTrait("Regeneration").TraitValue;
                HandleRegeneration(regenValue);
            }
            base.NewRound();
        }

        public abstract void HandleRegeneration(int regenValue);

        internal PathfinderDamage AdjustDamageByResistances(PathfinderDamage receivedDamage)
        {
            String descriptor = receivedDamage.DamageDescriptor;
            IEnumerable<StringTrait> stringTraits = (from trait in Traits
                where trait as StringTrait != null
                select trait as StringTrait);
            StringTrait immuneTrait =
                stringTraits.FirstOrDefault(x => x.TraitLabel.Equals("Immunity")
                                                 && x.TraitContents.Equals(descriptor));
            if (immuneTrait != null)
            {
                return new PathfinderDamage(receivedDamage.DamageDescriptor, 0);
            }
            NumericIntTrait resistTrait =
                NumericTraits.FirstOrDefault(x => x.TraitLabel.Equals("Resist") && x.TraitDescription.
                    Equals(descriptor));
            if (resistTrait != null)
            {
                int count = receivedDamage.DamageValue - resistTrait.TraitValue;
                if (count < 0)
                    count = 0;
                return new PathfinderDamage(receivedDamage.DamageDescriptor, count);
            }
            NumericIntTrait damageReductionTrait =
                NumericTraits.FirstOrDefault(x => x.TraitLabel.Equals("Damage Resistance"));
            if (damageReductionTrait != null)
            {
                if (!damageReductionTrait.TraitDescription.Equals(receivedDamage.DamageDescriptor))
                {
                    int count = receivedDamage.DamageValue - damageReductionTrait.TraitValue;
                    if (count < 0)
                        count = 0;
                    return new PathfinderDamage(receivedDamage.DamageDescriptor, count);
                }
                //TODO - I hate SRD damage reduction rules.
            }
            return receivedDamage;
        }

        internal virtual List<PathfinderDamage> HandleAttackResults(PathfinderDicePool curDamage, int damageMultiplier,
            PathfinderCharacter pathfinderTarget,
            String damageDescriptor, bool wasCrit)
        {
            curDamage.DiceQuantity = curDamage.DiceQuantity*damageMultiplier;
            curDamage.Modifier = curDamage.Modifier*damageMultiplier;
            curDamage.Roll();
            Report(curDamage.PoolDescription + " = " + curDamage.TotalValue + " " + damageDescriptor);
            PathfinderDamage doneDamage =
                pathfinderTarget.AdjustDamageByResistances(new PathfinderDamage(damageDescriptor,
                    curDamage.TotalValue));
            if (doneDamage.DamageValue <= 0)
            {
                Report("\t" + Target.Name + " resisted all damage");
                return null;
            }
            pathfinderTarget.DoDamage(doneDamage.DamageValue, damageDescriptor);
            Report("\n\t" + pathfinderTarget.Name + " took " + doneDamage.DamageValue + " " + damageDescriptor);
            return new List<PathfinderDamage> {doneDamage};
        }


        internal override List<Damage> AttackTarget(int RollModifier)
        {
            _rollResults = "";
            var damage = new List<Damage>();
            var pathfinderTarget = Target as PathfinderCharacter;
            int targetDefense = 0;
            var ChosenAttackTrait = FindNumericTrait(ChosenAttack) as AttackTrait;
            NumericIntTrait defenseTrait = pathfinderTarget.FindNumericTrait(ChosenAttackTrait.DefenseTarget);
            if (defenseTrait != null)
            {
                targetDefense = defenseTrait.TraitValue;
            }
            var allAttacks = new List<PathfinderAttackTrait> {ChosenAttackTrait as PathfinderAttackTrait};
            foreach (var cur in OtherAttackTraits)
            {
                NumericIntTrait matchingAttack = FindNumericTrait(cur);
                if (matchingAttack != null)
                {
                    var matchingPathfinderAttack = matchingAttack as PathfinderAttackTrait;
                    if (matchingPathfinderAttack != null)
                    {
                        allAttacks.Add(matchingPathfinderAttack);
                    }
                }
            }
            foreach (var attack in allAttacks)
            {
                foreach (var curBase in attack.ToHitBonusList)
                {
                    int curBonus = curBase + RollModifier;
                    var curAttack = new PathfinderDicePool(1, 20, 0);
                    curAttack.Roll();
                    int hitValue = curAttack.TotalValue + curBonus + StatusEffectBasedRollModifier;
                    String attackRollDesc = GetAttackRollDesc(attack: attack,
                        curDiePool: curAttack, 
                        curAttackBonus: curBonus,
                        targetDefense: targetDefense);
                    Report(Name + " attacked with " + attack.TraitDescription + ": "+attackRollDesc);
                    if (curAttack.TotalValue != 1 &&
                        (curAttack.TotalValue == 20 || hitValue >= targetDefense))
                    {
                        Report("\tDamage: ");
                        for (int curIndex = 0; curIndex < attack.DamageDice.Count; curIndex++)
                        {
                            int damageMultiplier = 1;
                            bool wasCrit = false;
                            //If the damage is 1d8 + 1d6, as with a flaming longsword, the flaming damage is not multiplied on a critical hit
                            if (curIndex == 0)
                            {
                                if (curAttack.TotalValue >= attack.LowestValueToCrit)
                                {
                                    curAttack.Roll();
                                    hitValue = curAttack.TotalValue + curBonus + StatusEffectBasedRollModifier;
                                    attackRollDesc = GetAttackRollDesc(attack: attack,
                                        curDiePool: curAttack,
                                        curAttackBonus: curBonus,
                                        targetDefense: targetDefense);
                                    Report("Crit confirm: "+attackRollDesc);
                                    if (curAttack.TotalValue != 1 &&
                                        (curAttack.TotalValue == 20 || hitValue >= targetDefense))
                                    {
                                        Report("--Crit confirmed--");
                                        damageMultiplier = attack.CritMultipier;
                                        wasCrit = true;
                                    }
                                    else
                                    {
                                        Report("--Crit fails--");
                                    }
                                }
                            }
                            Report("\n\t");
                            PathfinderDicePool curDamage = attack.DamageDice[curIndex].CopyPool();
                            string damageDescriptor = attack.DamageDescriptors[curIndex];
                            if (DamageType != null)
                                damageDescriptor = DamageType;

                            List<PathfinderDamage> doneDamage = pathfinderTarget.HandleAttackResults(curDamage,
                                damageMultiplier, pathfinderTarget,
                                damageDescriptor, wasCrit);
                            if (doneDamage != null)
                                damage.AddRange(doneDamage);
                        }
                    }
                    Report("\n");
                    if (SingleAttackOnly)
                    {
                        break;
                    }
                }
                if (SingleAttackOnly)
                {
                    break;
                }
            }
            return damage;
        }

        private string GetAttackRollDesc(PathfinderAttackTrait attack, PathfinderDicePool curDiePool, int curAttackBonus, int targetDefense)
        {
            int hitValue = curDiePool.TotalValue + curAttackBonus + StatusEffectBasedRollModifier;   
            String desc = "Rolled " +
                               curDiePool.TotalValue + "+" +
                               curAttackBonus;
            if (StatusEffectBasedRollModifier != 0)
            {
                int absVal = Math.Abs(StatusEffectBasedRollModifier);
                String posNegDesc = "";
                if (StatusEffectBasedRollModifier > 0)
                {
                    posNegDesc = "+";
                }
                else
                {
                    posNegDesc = "-";
                }
                desc = desc + posNegDesc + absVal.ToString();
            }
            desc = desc + "=" + hitValue
                   + " VS " + attack.DefenseTarget + " of " + targetDefense;
            return desc;
        }

        internal override DicePool RollBasePool(List<Trait> dicePools, int modifier)
        {
            IEnumerable<PathfinderNumericTrait> rollabletraits = (from trait in dicePools
                where trait as PathfinderNumericTrait != null
                select trait as PathfinderNumericTrait);
            foreach (var cur in rollabletraits)
            {
                modifier += cur.TraitValue;
            }
            var pool = new PathfinderDicePool(1, 20, modifier);
            pool.Roll();
            return pool;
        }

        [DataMember]
        public List<PathfinderLightSource> MobileLightSources
        {
            get { return _mobileLightSources; }
            set { _mobileLightSources = value; }
        }

        

        internal void AddLightSource(PathfinderLightSource pathfinderLightSource)
        {
            MobileLightSources.Add(pathfinderLightSource);
            pathfinderLightSource.Location = this.Location;
            NotifyStatusChange();
        }
        
        public override void HandleMovement(GameBoard.PieceMovedEventsArg pieceEvent)
        {
            base.HandleMovement(pieceEvent);
            foreach (var cur in MobileLightSources)
            {
                cur.Location = pieceEvent.Destination;
            }
        }

        protected String GetMobileLightDescription()
        {
            var sb = new StringBuilder();
            if (MobileLightSources.Any())
            {
                sb.AppendLine("\nCarrying lights: ");
            }
            foreach (var cur in MobileLightSources)
            {
                sb.AppendLine(cur.Description);
            }

            return sb.ToString();
        }

        public override void PopulateCombatTraits()
        {
            // traitList.Add(MakeSimpleNumericTrait("Darkvision", perceptArray[1]));
            // traitList.Add(MakeSimpleNumericTrait("Tremorsense", perceptArray[1]));
            // traitList.Add(MakeSimpleDescriptorTrait("Low-light Vision", textInfo.ToTitleCase(curTrim)));
            // traitList.Add(MakeSimpleNumericTrait("Blindsight", perceptArray[1]));
            // traitList.Add(MakeSimpleNumericTrait("Blindsense", perceptArray[1]));

            var traitDarkvision = FindNumericTrait("Darkvision");
            //Tremorsense would require some kind of notion of if the character is touching a surface the creature can 'see'
            //var traitTremorsense = FindNumericTrait("Tremorsense");
            var traitLowLightVision = FindStringTrait("Low-light Vision");
            var traitBlindsight = FindNumericTrait("Blindsight");
            var traitBlindsense = FindNumericTrait("Blindsense");
            if (traitDarkvision != null)
            {
                CharacterVisionType = new CharacterVisionType(CharacterVisionType.VisionType.Darkvision,
                                                              traitDarkvision.TraitValue);
            }
            if (traitBlindsight != null || traitBlindsense!=null)
            {
                //TODO -- the current model cannot handle having both darkvision and blindsight/sense

                //CharacterVisionType = new CharacterVisionType(CharacterVisionType.VisionType.,
                //                                              traitDarkvision.TraitValue);
            }
            if (traitLowLightVision != null)
            {
                if (CharacterVisionType.CurrentVisionType == CharacterVisionType.VisionType.Darkvision)
                {
                    CharacterVisionType.CurrentVisionType = CharacterVisionType.VisionType.LowLightAndDarkVision;
                }
                else
                {
                    CharacterVisionType = new CharacterVisionType(CharacterVisionType.VisionType.LowLight,
                                                              60);
                }
            }
        }

        protected override void AddSystemElements(XElement root)
        {
            XElement characterType = new XElement("CharacterType");
            characterType.Value = "Placeholder";
            root.Add(characterType);
        }


    }
}